///////////////////////////////////////////////////////////////////////////////
//
// pendolo.c - acquire data for SNS Lab Fis pendulum
// 
// 1st release: A. Lusiani <a.lusiani@sns.it> April 2003
//
// this program includes parts from poll.c, Part of Comedilib,
// Copyright (c) 1999,2000 David A. Schleef <ds@schleef.org>
// poll.c may be freely modified, distributed, and combined with
// other software, as long as proper attribution is given in the
// source code.
//

///////////////////////////////////////////////////////////////////////////////
//
// Modifications:
//
//

///////////////////////////////////////////////////////////////////////////////
//
// headers
//

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <comedilib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <ctype.h>
#include <string.h>
#include <sys/time.h>
#include <tgmath.h>

///////////////////////////////////////////////////////////////////////////////
//
// function declarations
//

void prepare_nonblocking_input(FILE* input);
void prepare_ai_scan(comedi_t *cifh,
		     int subdev,
		     comedi_cmd *cmdp,
		     unsigned int triggerType,
		     unsigned int sampleRate,
		     unsigned int scanType,
		     unsigned int scanNum,
		     unsigned int *chanList,
		     unsigned int chanListLen
		     );
void do_ai_scan(comedi_t *dev, int subdev, comedi_cmd *cmd);
void dump_cmd(FILE *out,comedi_cmd *cmd);

///////////////////////////////////////////////////////////////////////////////
//
// variable  declarations
//

comedi_t *device;

const char* 		COMEDI_DEV = "/dev/comedi0";
//--- comedi sub-devices for Analog Input
int			AI_SUBDEV;

//--- buffer size for streaming data out of the driver
const unsigned int	COMEDI_BUFSIZ =	(4*1024*1024);

//--- pendulum photo-cell analog input channel number
unsigned int 		PHCELL_AI_CHAN=0;
//--- pendulum photo-cell analog input minimum/amaximum voltage
// const float 		PHCELL_VMIN = 0;
// const float 		PHCELL_VMAX = 5;
const float 		PHCELL_VMIN = 0.7;
const float 		PHCELL_VMAX = 2.3;
//--- pendulum photo-cell analog input voltage reference: from GROUND
const unsigned int	PHCELL_AREF = AREF_GROUND;

//--- experimental value in the middle of maximum and minumum read (range=1)
const lsampl_t 		PHCELL_THRESH = 49673;
//--- analog in threshold for photo-cell, computed from range of ADC
lsampl_t phcellThresh;

//--- definitions to describe input channels for asynchronous DAQ
#define AISCAN_BEG	(0)
#define AISCAN_NUM	(1)
unsigned int aiscanList[AISCAN_NUM];

//--- rate at which the input is sampled
unsigned int		AISCAN_RATE = 100000;

//--- buffer size where reading from comedi device
#define BUFSIZE		(64*1024)
//--- ni_pcimio driver gives 16-bit unsigned samples
sampl_t buf[BUFSIZE];

///////////////////////////////////////////////////////////////////////////////
//
// code
//

int main(int argc, char *argv[])
{
  comedi_t *cifh;
  comedi_cmd cmd;
  int i;
  int rc;
  unsigned long pageSize;
  unsigned int pageNum;
  lsampl_t maxdata;
  double minvolts, maxvolts;
  double val;
  unsigned int rangeType;
  comedi_range* rangep;
  
  //--- endpoints of acquisition are valid numbers
  rc = comedi_set_global_oor_behavior(COMEDI_OOR_NUMBER);

  cifh = comedi_open(COMEDI_DEV);
  if(!cifh){
    perror(COMEDI_DEV);
    exit(1);
  }

  //--- get virtual memory page size
  pageSize = sysconf(_SC_PAGE_SIZE);
  //--- get minimum number of pages needed for requested buffer size
  pageNum = (COMEDI_BUFSIZ+1)/pageSize;
  //--- set comedi device streaming buffer size
  rc = comedi_set_buffer_size(cifh, AI_SUBDEV, pageNum*pageSize);
  if(rc<0){
    fprintf(stderr, "comedi_set_buffer_size: %s\n",strerror(errno));
    exit(1);
  }else{
    fprintf(stderr, "buffer size set to %d\n", rc);
  }

  //--- find which subdevice offers Analog Input
  rc = comedi_find_subdevice_by_type(cifh, COMEDI_SUBD_AI, 0);
  if (rc<0){
    comedi_perror("comedi_find_device AI");
    exit(1);
  }
  AI_SUBDEV = rc;

  //--- find smallest analog input range for 0 Volt .. 5 Volt
  rc = comedi_find_range(cifh, AI_SUBDEV, PHCELL_AI_CHAN, UNIT_volt, 0, 5);
  if (rc<0){
    comedi_perror("comedi_find_range");
    exit(1);
  }
  rangeType = rc;

  //--- get ptr to range struct
  rangep = comedi_get_range(cifh, AI_SUBDEV, PHCELL_AI_CHAN, rangeType);
  //--- get maximum possible sample for photo-cell analog input
  maxdata = comedi_get_maxdata(cifh, AI_SUBDEV, PHCELL_AI_CHAN);
  minvolts = comedi_to_phys(0, rangep, maxdata);
  maxvolts = comedi_to_phys(maxdata, rangep, maxdata);

  //--- where photo-cell input mean value falls inside selected range?
  val = ((PHCELL_VMIN+PHCELL_VMAX)/2 - minvolts)/(maxvolts-minvolts);
  phcellThresh = (val*maxdata);

  fprintf(stderr, "Comedi offers range n. %d, %f .. %f, threshold %d\n",
	 rangeType, minvolts, maxvolts, phcellThresh);

  //--- build channel list with channel #, range, reference
  for(i=0; i<AISCAN_NUM; i++){
    aiscanList[i] = CR_PACK(AISCAN_BEG+i, rangeType, PHCELL_AREF);
  }

  //--- prepare AI scan for microphone's input
  prepare_ai_scan(cifh, AI_SUBDEV, &cmd,
		  TRIG_NOW, AISCAN_RATE, TRIG_NONE, 0,
		  aiscanList, AISCAN_NUM);

  do_ai_scan(cifh, AI_SUBDEV, &cmd);

  return 0;
}

//
// prepare_ai_scan()
// - prepare comedi command for Analog Input scan
//
  
void prepare_ai_scan(comedi_t *cifh,
		     int subdev,
		     comedi_cmd *cmdp,
		     unsigned int triggerType,
		     unsigned int sampleRate,
		     unsigned int scanType,
		     unsigned int scanNum,
		     unsigned int *chanList,
		     unsigned int chanListLen
		     )
{
  int rc;

  //--- clear cmd struct
  memset(cmdp, 0, sizeof(*cmdp));
  
  /* the subdevice that the command is sent to */
  cmdp->subdev = 	subdev;
  
  /* flags */
  cmdp->flags =		0;
  
  /* each event requires a trigger, which is specified
     by a source and an argument.  For example, to specify
     an external digital line 3 as a source, you would use
     src=TRIG_EXT and arg=3. */
  
  /* In this case, we specify using TRIG_NOW to start
   * acquisition immediately when the command is issued.  
   * The argument of TRIG_NOW is "number of nsec after
   * NOW", but no driver supports it yet.  Also, no driver
   * currently supports using a start_src other than
   * TRIG_NOW.  */
  cmdp->start_src =		triggerType;
  cmdp->start_arg =		0;
  
  /* The timing of the beginning of each scan is controlled
   * by scan_begin.  TRIG_TIMER specifies that scan_start
   * events occur periodically at a rate of scan_begin_arg
   * nanoseconds between scans. */
  cmdp->scan_begin_src =	TRIG_TIMER;
  cmdp->scan_begin_arg =	((unsigned int)1e9)/sampleRate;
  
  /* The timing between each sample in a scan is controlled
   * by convert.  Like above, TRIG_TIMER specifies that
   * convert events occur periodically at a rate of convert_arg
   * nanoseconds between scans. */
  cmdp->convert_src =	TRIG_TIMER;
  cmdp->convert_arg =	0;
  
  /* The end of each scan is almost always specified using
   * TRIG_COUNT, with the argument being the same as the
   * number of channels in the chanlist.  You could probably
   * find a device that allows something else, but it would
   * be strange. */
  cmdp->scan_end_src =	TRIG_COUNT;
  cmdp->scan_end_arg =	chanListLen;	/* number of channels */
  
  /* The end of acquisition is controlled by stop_src and
   * stop_arg.  The src will typically be TRIG_COUNT or
   * TRIG_NONE.  Specifying TRIG_COUNT will stop acquisition
   * after stop_arg number of scans, or TRIG_NONE will
   * cause acquisition to continue until stopped using
   * comedi_cancel(). */
  cmdp->stop_src =		scanType;
  cmdp->stop_arg =		scanNum;
  
  /* the channel list determined which channels are sampled.
     In general, chanlist_len is the same as scan_end_arg.  Most
     boards require this.  */
  cmdp->chanlist =	chanList;
  cmdp->chanlist_len =	chanListLen;

  fprintf(stderr, "Assembled DAQ command\n");
  dump_cmd(stderr, cmdp);
  rc = comedi_command_test(cifh, cmdp);
  fprintf(stderr, "1st comedi_command_test rc: %d\n",rc);
  if(rc<0){
    fprintf(stderr, "1st comedi_command_test errno: %d\n",errno);
    comedi_perror("comedi_command_test");
    return;
  }
  
  fprintf(stderr, "DAQ command modified by comedi\n");
  dump_cmd(stderr, cmdp);
  rc = comedi_command_test(cifh,cmdp);
  fprintf(stderr,"2nd comedi_command_test rc: %d\n",rc);
  if (rc<0) {
    fprintf(stderr,"2nd comedi_command_test errno: %d\n",errno);
    comedi_perror("comedi_command_test");
    //--- comedi failure, no scan command possible, quit
    exit(1);
  }

  return;
}

//
// do_ai_scan()
// - acquire data from Analog Input
//
  
void do_ai_scan(comedi_t *cifh, int subdev, comedi_cmd *cmdp)
{
  int rc;
  int go;
  int quit;
  unsigned int sample;
  unsigned int sampleLast;
  unsigned int sampleNew;
  unsigned int sampleNum;
  unsigned int readMax;
  unsigned int readMin;
  unsigned int counter;
  unsigned int counterLast;
  unsigned int counterNew;
  unsigned long counterOsc;
  unsigned int periodOsc;
  fd_set rdset;
  struct timeval timeout;

  //--- ask for non-blocking I/O from comedi device
  fcntl(comedi_fileno(cifh),F_SETFL,O_NONBLOCK);

  //--- prepare non blocking input from stdin for DAQ scan
  prepare_nonblocking_input(stdin);

  rc = comedi_command(cifh, cmdp);
  fprintf(stderr,"Start DAQ, rc= %d\n", rc);
  if (rc<0) {
    fprintf(stderr,"errno=%d\n",errno);
    comedi_perror("comedi_command");
    return;
  }

  go = 1;
  quit = 0;
  counter = 0;
  counterLast = 0;
  counterNew = 0;
  counterOsc = 0;
  periodOsc = 0;
  sampleLast = 0;
  sampleNew = 0;
  sampleNum = 0;
  readMax = 0;
  readMin = 0;

  //--- data acquisition loop
  while(go){
    FD_ZERO(&rdset);
    FD_SET(comedi_fileno(cifh),&rdset);
    FD_SET(fileno(stdin),&rdset);
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    //--- wait untill either timeout or some data are available
    rc = select(comedi_fileno(cifh)+1,&rdset,NULL,NULL,&timeout);
    // fprintf(stderr,"select returned %d\n",rc);
    if (rc<0) {
      //--- error in select
      perror("select");
    } else if (rc==0) {
      //--- no data available before timeout
      //--- ask for available data even if driver buffer is not full
      // fprintf(stderr,"timeout, polling...\n");
      rc = comedi_poll(cifh,AI_SUBDEV);
      // fprintf(stderr,"poll returned %d\n",rc);
    } else if (FD_ISSET(fileno(stdin),&rdset)) {
      //--- standard input has some characters available
      unsigned char ch = fgetc(stdin);
      if (ch == 25 && go) {
	fprintf(stderr,"  --- <control-Y> received, quitting ... --- \n");
	quit = 1;
      } else {
	fprintf(stderr,"  --- Press <control-Y> to quit --- \n");
      }
    } else if (FD_ISSET(comedi_fileno(cifh),&rdset)) {
      //--- comedi file descriptor became ready
      // fprintf(stderr,"comedi file descriptor ready\n");
      //--- read available data
      rc = read(comedi_fileno(cifh), buf, sizeof(buf));
      // fprintf(stderr,"read returned %d\n",rc);
      if (rc<0) {
	if (errno==EAGAIN) {
	  go = 0;
	  perror("read");
	}
      } else if (rc==0) {
	//--- if end of file reading comedi device then quit
	fprintf(stderr,"EOF reading comedi, quitting\n");
	go = 0;
      } else {
	//--- read available data
	unsigned int i;
	// fprintf(stderr,"read %d %d\n",rc,counter);
	for(i=0; i<(unsigned int)rc/sizeof(sampl_t); i++,
	      counter++,
	      sampleLast = sampleNew,
	      counterLast = counterNew
	    ){

	  // fprintf(stderr, "0x%x\n", buf[i]);

	  //--- sample = 1 if input above threshold, 0 otherwise
	  sample = (buf[i] > phcellThresh);

	  //--- first sample, initialize
	  if (sampleNum == 0) {
	    sampleNum++,
	    readMin = buf[i];
	    readMax = buf[i];
	    sampleNew = sample;
	    counterNew = counter;
	    continue;
	  }

	  if (buf[i] < readMin) {
	    readMin = buf[i];
	  }
	  if (buf[i] > readMax) {
	    readMax = buf[i];
	  }
	  
	  //--- wait untill transition
	  if (sample == sampleLast) continue;
	  
	  //--- note new sample and counter for each transition
	  sampleNew = sample;
	  counterNew = counter;
	  sampleNum++;

	  //--- discard 1st time difference (2nd sample)
	  if (sampleNum <=2) continue;
	  
	  //--- begin acquisition with transition from dark to light
	  if (sampleNum <=3 && sample > sampleLast) continue;
	  
	  periodOsc += counter-counterLast;
	  //--- transition: save time diff. w.r.t. last sample counter
	  printf("%6d ", counter-counterLast);
	  //--- print 4 times per line
	  if ( (counterOsc++ %4 ) == 3 ) {
#if 0	    
	    printf("min=%d max=%d mean=%d ",
		   readMin, readMax, (readMin+readMax)/2);
#endif
	    printf("%6d\n", periodOsc);
	    periodOsc = 0;
	    fflush(stdout);
	    //--- if quit requested, end after completing cycle
	    if (quit) {
	      go = 0;
	    }
	  }
	}
      }
    } else {
      /* unknown file descriptor became ready */
      fprintf(stderr,"Unknown file descriptor ready\n");
    }
  }

  //--- cancel endless DAQ
  rc = comedi_cancel(cifh, subdev);
}

//
// prepare_nonblocking_input()
// - setup terminal input for non-blocking input
//

void prepare_nonblocking_input(FILE* input)
{
  struct termios attr;

  //--- if input is a terminal, ask not to wait for newlines, no echo
  if (tcgetattr(fileno(input), &attr) == 0) {
    //--- echo off
    attr.c_lflag &= ~(ECHO);
    //--- do not wait for newline
    attr.c_lflag &= ~(ICANON);
    //--- read at least one char
    attr.c_cc[VMIN] = 1;
    // set new terminal attrs
    tcsetattr(fileno(input), TCSAFLUSH, &attr);
  }
}

//
// cmd_src()
// - decode src field of comedi_cmd structure
//

char *cmd_src(int src,char *buf)
{
  buf[0]=0;
  
  if(src&TRIG_NONE)strcat(buf,"none|");
  if(src&TRIG_NOW)strcat(buf,"now|");
  if(src&TRIG_FOLLOW)strcat(buf, "follow|");
  if(src&TRIG_TIME)strcat(buf, "time|");
  if(src&TRIG_TIMER)strcat(buf, "timer|");
  if(src&TRIG_COUNT)strcat(buf, "count|");
  if(src&TRIG_EXT)strcat(buf, "ext|");
  if(src&TRIG_INT)strcat(buf, "int|");
#ifdef TRIG_OTHER
  if(src&TRIG_OTHER)strcat(buf, "other|");
#endif
  
  if(strlen(buf)==0){
    sprintf(buf,"unknown(0x%08x)",src);
  }else{
    buf[strlen(buf)-1]=0;
  }
  
  return buf;
}

//
// dump_cmd()
// - decode comedi_cmd structure
//

void dump_cmd(FILE *out,comedi_cmd *cmd)
{
  char buf[100];
  
  fprintf(out,"--- begin comedi command\n");
  fprintf(out,"start:      %-8s %d\n",
	  cmd_src(cmd->start_src,buf),
	  cmd->start_arg);
  
  fprintf(out,"scan_begin: %-8s %d\n",
	  cmd_src(cmd->scan_begin_src,buf),
	  cmd->scan_begin_arg);
  
  fprintf(out,"convert:    %-8s %d\n",
	  cmd_src(cmd->convert_src,buf),
	  cmd->convert_arg);
  
  fprintf(out,"scan_end:   %-8s %d\n",
	  cmd_src(cmd->scan_end_src,buf),
	  cmd->scan_end_arg);
  
  fprintf(out,"stop:       %-8s %d\n",
	  cmd_src(cmd->stop_src,buf),
	  cmd->stop_arg);
  fprintf(out,"--- end comedi command\n");
}

